package com.hulman.oms.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.jdbc.Null;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class SqlExecutor
{
    private final Connection connection;
    private final TypeHandlerRegistry typeHandlerRegistry;

    public SqlExecutor(Connection connection)
    {
        this.connection = connection;
        this.typeHandlerRegistry = new TypeHandlerRegistry();
    }

    /**
     * Executes a SELECT statement that returns multiple rows.
     *
     * @param sql  The SQL
     * @param args The arguments to be set on the statement.
     * @return The list of rows expected.
     * @throws SQLException If statement preparation or execution fails
     */
    public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException
    {
        try (PreparedStatement ps = connection.prepareStatement(sql); ResultSet rs = ps.executeQuery())
        {
            //setParameter
            for (int i = 0, n = args.length; i < n; i++)
            {
                if (args[i] == null)
                {
                    throw new SQLException("SqlRunner requires an instance of Null to represent typed null values for JDBC compatibility");
                } else if (args[i] instanceof Null)
                {
                    ((Null) args[i]).getTypeHandler().setParameter(ps, i + 1, null, ((Null) args[i]).getJdbcType());
                } else
                {
                    TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(args[i].getClass());
                    if (typeHandler == null)
                    {
                        throw new SQLException("SqlRunner could not find a TypeHandler instance for " + args[i].getClass());
                    } else
                    {
                        typeHandler.setParameter(ps, i + 1, args[i], null);
                    }
                }
            }
            //getResult
            List<Map<String, Object>> list = new ArrayList<>();
            List<String> columns = new ArrayList<>();
            List<TypeHandler<?>> typeHandlers = new ArrayList<>();
            ResultSetMetaData rsmd = rs.getMetaData();
            for (int i = 0, n = rsmd.getColumnCount(); i < n; i++)
            {
                columns.add(rsmd.getColumnLabel(i + 1));
                try
                {
                    Class<?> type = Resources.classForName(rsmd.getColumnClassName(i + 1));
                    TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(type);
                    if (typeHandler == null)
                    {
                        typeHandler = typeHandlerRegistry.getTypeHandler(Object.class);
                    }
                    typeHandlers.add(typeHandler);
                } catch (Exception e)
                {
                    typeHandlers.add(typeHandlerRegistry.getTypeHandler(Object.class));
                }
            }
            while (rs.next())
            {
                Map<String, Object> row = new LinkedHashMap<>();
                for (int i = 0, n = columns.size(); i < n; i++)
                {
                    String name = columns.get(i);
                    TypeHandler<?> handler = typeHandlers.get(i);
                    row.put(name, handler.getResult(rs, name));
                }
                list.add(row);
            }
            return list;
        }
    }
}
